package org.example.repository.dialect.mysql;

import lombok.extern.slf4j.Slf4j;
import org.example.repository.*;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
public class MysqlRepository<T extends Entity<?>> implements Repository<T> {

    private static final CountRowMapper countRowMapper = new CountRowMapper();

    private final Schema schema;
    private final JdbcTemplate jdbcTemplate;
    private final EntityRowMapper<T> entityRowMapper;

    private final Executor executor = new Executor();

    @SuppressWarnings("unchecked")
    public MysqlRepository(Schema schema, JdbcTemplate jdbcTemplate) {
        this.schema = schema;
        this.jdbcTemplate = jdbcTemplate;
        this.entityRowMapper = (EntityRowMapper<T>) new EntityRowMapper<>(schema.entityClass());
    }

    @Override
    public List<T> find(Query query) {
        return executor.executeQuery(query);
    }

    @Override
    public long count(Query query) {
        return executor.executeCount(query);
    }

    @Override
    public T findById(Object id) {
        Query _query = new Query().where(Condition.equals("id", id)).limit(1);
        return executor.executeQuery(_query).stream().findFirst().orElse(null);
    }

    @Override
    public T findFirst(Query query) {
        Query _query = new Query(query).limit(1);
        return executor.executeQuery(_query).stream().findFirst().orElse(null);
    }

    @Override
    public int add(T entity) {
        return executor.executeInsert(mapToMap(entity));
    }

    @Override
    public int update(T entity, Condition where) {
        return executor.executeUpdate(mapToMap(entity), where);
    }

    @Override
    public void clear() {
        executor.executeClear();
    }

    private Map<String, Object> mapToMap(T entity) {
        BeanWrapperImpl bw = new BeanWrapperImpl();
        bw.setBeanInstance(entity);
        Map<String, Object> entityMap = new HashMap<>();
        for (Property property : schema.properties()) {
            String name = property.name();
            Object value = bw.getPropertyValue(name);
            entityMap.put(name, value);
        }
        return entityMap;
    }

    private class Executor {

        List<T> executeQuery(Query query) {
            SqlStatement result = new SqlBuilder(schema)
                    .query(query)
                    .buildSelectStatement();
            String sql = result.sql;
            Object[] values = result.values;
            log.info("sql: {}", sql);
            return jdbcTemplate.query(sql, entityRowMapper, values);
        }

        long executeCount(Query query) {
            SqlStatement result = new SqlBuilder(schema)
                    .query(query)
                    .buildCountStatement();
            String sql = result.sql;
            Object[] values = result.values;
            log.info("sql: {}", sql);

            Long count = jdbcTemplate.queryForObject(sql, countRowMapper, values);
            if (count == null) {
                throw new RuntimeException("count failed.");
            }
            return count;
        }

        int executeInsert(Map<String, Object> entity) {
            SqlStatement result = new SqlBuilder(schema)
                    .entity(entity)
                    .buildInsertStatement();
            String sql = result.sql;
            Object[] values = result.values;
            log.info("sql: {}", sql);

            int effected = jdbcTemplate.update(sql, values);
            log.info("effected: {}", effected);
            return effected;
        }

        int executeUpdate(Map<String, Object> entity, Condition where) {
            SqlStatement result = new SqlBuilder(schema)
                    .entity(entity)
                    .query(new Query().where(where))
                    .buildUpdateStatement();
            String sql = result.sql;
            Object[] values = result.values;
            log.info("sql: {}", sql);

            int effected = jdbcTemplate.update(sql, values);
            log.info("effected: {}", effected);
            return effected;
        }

        void executeClear() {
            SqlStatement result = new SqlBuilder(schema).buildClearStatement();
            String sql = result.sql;
            Object[] values = result.values;
            log.info("sql: {}", sql);

            jdbcTemplate.update(sql, values);
        }
    }
}
